<?php

$active_tests =array(
	/*
	'class_allfeed',
	 */
	'class_auth_digest',
	/*
	'class_comment',
	'class_community',
	'class_doc',
	'class_emails',
	'class_file',
	'class_filestorage',
	'class_imagestorage',
	'class_image',
	'class_issue',
	'class_link',
	'class_mail_outbox',
	'class_node',
	'class_pid_user',
	'class_register',
	'class_role',
	'class_subscribers',
	'class_tag',
	'class_tdi_order',
	'class_user',
	'class_tdi',
	'objects',
	'tag_subject',
	't_acl_list',
	't_acl_tree',
	't_subject_relations',
	 */
);
define ('CSVLOG_DIR', '/tmp/');




error_reporting(E_ALL | E_STRICT);
date_default_timezone_set('Asia/Yekaterinburg');


mb_language('ru');
mb_internal_encoding('utf-8');
mb_detect_order('utf-8, windows-1251');


function main()
{
	$_basedir = dirname(dirname(__FILE__));

	setlocale(LC_ALL, array("ru_RU.UTF-8","ru_RU","russian","ru_SU","ru"));

	// log dir
	$logdir = CSVLOG_DIR;

	// connect DB
	require_once $_basedir.'/etc/conf.d/db.php';
	$db = new MysqlDB($config);
	$db->connect();
	unset($config);

	// load tests
	/*
	$tests['foo'] = array(
		'queries' => array(
			'select * from foor',
		),
		'queries_args' => array(
			'count' => 10,
			'checkpoints' => array(1, 3, 5),
		),
		'timelimit' => 0,
	);
	 */
	require_once dirname(__FILE__).'/sqltest.data.php';
	$tests = $self;
	unset($self);

	global $active_tests;
	foreach ($tests as $k=>$v) { if (!in_array($k, $active_tests)) unset($tests[$k]); }

	foreach ($tests as $name=>$test) {
		CSVLog::open($logdir.$name.'.csv');
### debug(__METHOD__,"Run group test '$name'");
		$tc = new TestCase($test, new TestCaseGListener(), $db);
		$status = $tc->run();
### debug(__METHOD__,"Stop group test '$name'");
		$msg = ($status) ? 'Ok' : 'Failed';
		echo "group test '$name' [$msg]\n";
		CSVLog::close();
	}
}
main();

class TestCaseListener
{
	public function __construct ()
	{
		$this->tc = NULL;
		$this->s = array(
			 'test_start' => 0,
			  'test_time' => NULL,
			   'test_end' => NULL,
			'query_start' => NULL,
			'query_count' => 0,
			  'query_end' => NULL,
			   'cp_start' => NULL,
			    'cp_prev' => NULL,
		);
	} // end of __construct

	public function test_start ()
	{
		$this->s['test_start'] = microtime(true);
### debug(__METHOD__,"Test start");
	} // end of method test_start
	public function test_end ()
	{
		$this->s['test_end'] = microtime(true);
### debug(__METHOD__,sprintf("Test end %s", $this->s['test_end']));
	} // end of method test_end
	public function test_timelimit ()
	{
		$this->query_end();
	} // end of method test_timelimit
	public function query_start ($query)
	{
		$this->s['query_start'] = microtime(true);
### debug(__METHOD__,sprintf("Query start: %s, '%s'", $this->s['query_start'], $query));
	} // end of method query_start
	public function query_end ($query, $errno, $error)
	{
		$this->s['query_end'] = microtime(true);
### debug(__METHOD__,sprintf("Query end %s, execution time %0.00f", $this->s['query_end'], $this->s['query_end'] - $this->s['query_start']));
		CSVLog::log(array(
			$this->s['query_end'] - $this->s['test_start'],  // test execution time
			$this->s['query_end'] - $this->s['cp_start'],    // execution time from checkpoint
			$query,                                          // raw SQL
			$this->s['query_end'] - $this->s['query_start'], // query execution time
			$errno,                                          // sql server error number
			$error,                                          // sql server error message
		));
	} // end of method query_end
	public function checkpoint ()
	{
		$this->s['cp_prev'] = $this->s['cp_start'];
		$this->s['cp_start'] = microtime(true);
### debug(__METHOD__,sprintf("Set checkpoint"));
		CSVLog::log(array(
			$this->s['cp_start'] - $this->s['test_start'],   // test execution time
			'',                                              // execution time from checkpoint
			'__CHECKPOINT__',                                // raw SQL
			'',                                              // query execution time
			'',                                              // sql server error number
			'',                                              // sql server error message
		));
	} // end of method checkpoint
}
class TestCaseGListener extends TestCaseListener
{
	public function query_start ($query)
	{
		$this->s['query_start'] = microtime(true);
		$this->s['query_count'] += 1;
### debug(__METHOD__,sprintf("Query start: %s, '%s'", $this->s['query_start'], $query));
	} // end of method query_start
	public function query_end ($query, $errno, $error)
	{
		$this->s['query_end'] = microtime(true);
		$this->q[$query] = $error ? $error : ($this->s['query_end'] - $this->s['query_start']);
### debug(__METHOD__,sprintf("Query end %s, execution time %0.00f", $this->s['query_end'], $this->s['query_end'] - $this->s['query_start']));
	} // end of method query_end
	public function iteration_start ()
	{
		$this->s['iteration_start'] = microtime(true);
		$this->q = array();
### debug(__METHOD__,"Iteration start");
	} // end of method iteration_start

	public function iteration_end ()
	{
		$this->s['iteration_end'] = microtime(true);
### debug(__METHOD__,sprintf("Iteration end %s", $this->s['iteration_end']));
		$data = array(
			$this->s['query_end'] - $this->s['test_start'],  // test execution time
			$this->s['query_end'] - $this->s['cp_start'],    // execution time from checkpoint
		);
		foreach ($this->q as $sql=>$time) {
			$data[] = $time;
		}
		CSVLog::log($data);
	} // end of method iteration_end
}
class TestCase
{
	public function __construct ($config, $listener, $db)
	{
		if (!isset($config['before']))       $config['before'] = array();
		if (!isset($config['before_args']))  $config['before_args'] = array();
		if (!isset($config['after']))        $config['after'] = array();
		if (!isset($config['after_args']))   $config['after_args'] = array();
		if (!isset($config['queries']))      $config['queries'] = array();
		if (!isset($config['queries_args'])) $config['queries_args'] = array();
		if (!isset($config['timelimit']))    $config['timelimit'] = NULL;
		$this->config = $config;
		$this->listener = $listener;
		$this->db = $db;
	} // end of __construct

	public function run ()
	{
		$status = true;

		$this->listener->tc = $this;
		$this->active = true;
		$this->test_start = microtime(true);
		$c = $this->config;
		$this->listener->test_start();
### debug(__METHOD__,'-- Before');
		if ($this->active) $status = ($this->run_stage($c['before'], $c['before_args']) && $status);
### debug(__METHOD__,'-- Queries');
		$this->listener->checkpoint();
		if ($this->active) $status = ($this->run_stage($c['queries'], $c['queries_args']) && $status);
### debug(__METHOD__,'-- After');
		/*if ($this->active) */$status = ($this->run_stage($c['after'], $c['after_args']) && $status);
		$this->listener->test_end();

		return $status;
	} // end of method run
	public function run_stage ($queries, $args)
	/**
	 * Запустить пачку SQL.
	 * 
	 * @param array $queries пачка SQL
	 * @param array $args параметры
	 * @access public
	 * @return bool результат теста
	 */
	{
		$status = !empty($queries);

		if ($queries) {
			$Z         = isset($args['count'])       ? $args['count'] : 1;
			$cp_map    = isset($args['checkpoints']) ? array_flip($args['checkpoints']): array();
			$timelimit = $this->config['timelimit'];
			$D         = isset($args['dis'])         ? $args['dis'] : false;

			$j=0; $zz = microtime(true);
			for($i=0; $i<$Z; $i++) {
				if (array_key_exists($i, $cp_map)) {
					$this->listener->checkpoint();
				}
				$this->listener->iteration_start();
				if (!($i%100)) { $_zz = microtime(true); debug(__METHOD__, sprintf('%d %f qps', $i, $j / ($_zz - $zz))); $j=0; $zz = $_zz; }
				foreach ($queries as $query) {
					if ($D && !($j%100)) { 
						$this->listener->iteration_start();
						$_zz = microtime(true); debug(__METHOD__, sprintf('%f qps', $j / ($_zz - $zz))); $j=0; $zz = $_zz; 
					}
					$now = microtime(true);
					if ($timelimit && ($now - $this->test_start > $timelimit)) {
						$this->listener->test_timelimit();
						$this->active = false;
						$status = false;
						break 2;
					}
					$this->listener->query_start($query);
					$this->db->execute($query, $errno, $error);
					$this->listener->query_end($query, $errno, $error);
					$status = (empty($error) && $status);
					if ($error) {
						error(__METHOD__, 'test failed');
					}
					$j++;
					if ($D && !($j%100)) { 
						$this->listener->iteration_end();
					}
				}
				$this->listener->iteration_end();
			}
		}

		return $status;
	} // end of method run_stage
}
class MysqlDB
{
	public function __construct ($config)
	{
		$this->config = $config;
	} // end of __construct
	public function connect ()
	{
		$c = $this->config;
		$this->db = mysql_connect($c['db_host'], $c['db_user'], $c['db_password']);
		if (!$this->db) {
		}
		mysql_select_db($this->config['db_name'], $this->db);
	} // end of method connect
	public function execute ($sql, &$errno=NULL, &$error=NULL)
	{
		mysql_query($sql, $this->db);
		if ($errno = mysql_errno()) {
			$error = mysql_error();
			error(__METHOD__, $error, $errno);
		}
	} // end of method execute
}

function debug ($facility, $msg)
{
	$out = sprintf("%s [%s]\n%20.20s%s\n\n", date('Y-m-d H:i:s'), $facility, '', $msg, "\n");
	echo $out;
	flush();
} // end of function debug
function error($facility, $msg, $errno=NULL)
{
	$out = sprintf("%s [%s]\n%18.18s! %s\n\n", date('Y-m-d H:i:s'), $facility, '', $msg, "\n");
	echo $out;
	flush();
} // end of function debug
class CSVLog
{
	static public $fp;

	static function open($file)
	{
		self::close();
		self::$fp = fopen($file, 'w');
	}

	static public function close()
	{
		@fclose(self::$fp);
	}
	static public function log ($args)
	{
		$out = '';
		$tr = array('"' => '\\"',);
		foreach ($args as $arg) {
			if (is_float($arg)) {
				$value = sprintf('%f', $arg); // locale aware float to string convertion
			} else {
				$quot = is_int(strpos($arg, ';')) || is_int(strpos($arg, "\n"))? '"' : '';
				$value = $quot.strtr($arg, $tr).$quot;
			}
			if ($out) {
				$out .= ';'.$value;
			} else {
				$out .= $value;
			}
		}
		$out .= "\n";

		fwrite(self::$fp, $out);
	} // end of function CSVLog::log
}


